﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using BattleCity.GameLogic;

namespace BattleCity.Rendering
{
    class BlocksRenderer
    {
        struct BlockIndexesInfo
        {
            public int startIndexOffset;
            public List<Tuple<short,short>> bricksIndex;
        }

        VertexBuffer[] blocksVertexBuffers;
        IndexBuffer[] blocksIndexBuffers;

        public VertexBuffer[] vertexBuffers { get { return blocksVertexBuffers; } }
        public IndexBuffer[] indexBuffers { get { return blocksIndexBuffers; } }

        VertexPositionNormalTexture[][] vertexData;
        short[][] indexData;

        int[] brickCount;

        BlockIndexesInfo[,] blockIndexesInfos;

        GraphicsDevice graphicsDevice;

        public BlocksRenderer(GraphicsDevice graphicsDevice)
        {
            this.graphicsDevice = graphicsDevice;

            //devo calcolare quanti mattoncini ci sono in tutto
            brickCount = new int[2];
            brickCount[0] = brickCount[1] = 1;

            for (int i = 0; i < 13; ++i)
            {
                for (int j = 0; j < 13; ++j)
                {
                    int index = 0;
                    if (i > 6) index = 1;

                    int n = 0;

                    bool[,] brickMatrix = Types.getCurrentBricks(GameState.gameState.BlockGrid[i, j].makeUp);

                    if (GameState.gameState.BlockGrid[i, j].makeUp.IsCeramicBrick) n = 4;
                    if (GameState.gameState.BlockGrid[i, j].makeUp.IsSteelBrick) n = 2;

                    for (int c = 0; c < n; ++c)
                    {
                        for (int k = 0; k < n; ++k)
                        {
                            if (brickMatrix[c, k])
                                brickCount[index]++;
                        }
                    }
                }
            }

            vertexData = new VertexPositionNormalTexture[2][];
            indexData = new short[2][];

            //dichiaro i buffers sulla base del numero di mattoncini calcolati
            blocksVertexBuffers = new VertexBuffer[2];
            blocksIndexBuffers = new IndexBuffer[2];

            //i vertex buffer contengono 16 vertici per ogni mattoncino, più un vertice "spazzatura" dove verranno indirizzati gli indici dell'indexBuffer relativi
            //ai mattoncini distrutti e quindi non più visibili.
            blocksVertexBuffers[0] = new VertexBuffer(graphicsDevice, VertexPositionNormalTexture.VertexDeclaration, (brickCount[0] * 16) + 1, BufferUsage.None);
            blocksIndexBuffers[0] = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, brickCount[0] * 24, BufferUsage.None);

            blocksVertexBuffers[1] = new VertexBuffer(graphicsDevice, VertexPositionNormalTexture.VertexDeclaration, (brickCount[1] * 16) + 1, BufferUsage.None);
            blocksIndexBuffers[1] = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, brickCount[1] * 24, BufferUsage.None);


            int[] vertexOffset = { 0, 0 };
            int[] indexOffset = { 0, 0 };

          
            vertexData[0] = new VertexPositionNormalTexture[(brickCount[0] * 16) + 1];
            vertexData[1] = new VertexPositionNormalTexture[(brickCount[1] * 16) + 1];

           
            indexData[0] = new short[brickCount[0] * 24];
            indexData[1] = new short[brickCount[1] * 24];

            blockIndexesInfos = new BlockIndexesInfo[13, 13];

            for (int i = 0; i < 13; ++i)
            {
                for (int j = 0; j < 13; ++j)
                {
                    int index = 0; if (i > 6) index = 1;

                    //posizione del blocco
                    Vector3 pos = GameState.gameState.BlockGrid[i, j].position.xnaVector3YView;

                    bool[,] brickMatrix = Types.getCurrentBricks(GameState.gameState.BlockGrid[i, j].makeUp);

                    int n = 0;
                    if (GameState.gameState.BlockGrid[i, j].makeUp.IsCeramicBrick) n = 4;
                    if (GameState.gameState.BlockGrid[i, j].makeUp.IsSteelBrick) n = 2;

                    if (n > 0)
                    {
                        blockIndexesInfos[i, j].startIndexOffset = indexOffset[index];
                        blockIndexesInfos[i, j].bricksIndex = new List<Tuple<short, short>>();
                    }

                    for (int c = 0; c < n; ++c)
                    {
                        for (int k = 0; k < n; ++k)
                        {
                            if (brickMatrix[c, k])
                            {
                                blockIndexesInfos[i, j].bricksIndex.Add(new Tuple<short, short>((short)c, (short)k));

                                if (GameState.gameState.BlockGrid[i, j].makeUp.IsCeramicBrick)
                                    fillCeramicBrickData(c, k, pos, vertexOffset[index], indexOffset[index], ref vertexData[index], ref indexData[index]);

                                if (GameState.gameState.BlockGrid[i, j].makeUp.IsSteelBrick)
                                    fillSteelBrickData(c, k, pos, vertexOffset[index], indexOffset[index], ref vertexData[index], ref indexData[index]);

                                vertexOffset[index] += 16;
                                indexOffset[index] += 24;
                            }
                        }
                    }
                }
            }

            //Inserisco il vertice "spazzatura nell'ultima locazione";
            vertexData[0][(brickCount[0] * 16)] = new VertexPositionNormalTexture(new Vector3(-1000, -1000, -1000), Vector3.UnitX, Vector2.Zero);
            vertexData[1][(brickCount[1] * 16)] = new VertexPositionNormalTexture(new Vector3(-1000, -1000, -1000), Vector3.UnitX, Vector2.Zero);

            blocksIndexBuffers[0].SetData<short>(indexData[0]);
            blocksIndexBuffers[1].SetData<short>(indexData[1]);

            blocksVertexBuffers[0].SetData<VertexPositionNormalTexture>(vertexData[0]);
            blocksVertexBuffers[1].SetData<VertexPositionNormalTexture>(vertexData[1]);
        }

        public int PrimitiveCount(int index)
        {
            return blocksVertexBuffers[index].VertexCount / 2;
        }

        void fillCeramicBrickData(int i, int j, Vector3 blockPosition, int vertexOffset, int indexOffset, ref VertexPositionNormalTexture[] vertexData, ref short[] indexData)
        {
            //the position of the bottom-left vertex
            float x = j - 2;
            float z = i - 1;

            //front face
            vertexData[vertexOffset + 0] = new VertexPositionNormalTexture(new Vector3(x, 0, z) + blockPosition, Vector3.UnitZ, new Vector2(j / 8.0f, 1.0f));
            vertexData[vertexOffset + 1] = new VertexPositionNormalTexture(new Vector3(x, 2, z) + blockPosition, Vector3.UnitZ, new Vector2(j / 8.0f, 0.5f));
            vertexData[vertexOffset + 2] = new VertexPositionNormalTexture(new Vector3(x + 1, 2, z) + blockPosition, Vector3.UnitZ, new Vector2((j + 1) / 8.0f, 0.5f));
            vertexData[vertexOffset + 3] = new VertexPositionNormalTexture(new Vector3(x + 1, 0, z) + blockPosition, Vector3.UnitZ, new Vector2((j + 1) / 8.0f, 1.0f));

            indexData[indexOffset + 0] = (short)(vertexOffset + 0);
            indexData[indexOffset + 1] = (short)(vertexOffset + 1);
            indexData[indexOffset + 2] = (short)(vertexOffset + 2);
            indexData[indexOffset + 3] = (short)(vertexOffset + 2);
            indexData[indexOffset + 4] = (short)(vertexOffset + 3);
            indexData[indexOffset + 5] = (short)(vertexOffset + 0);

            //right face
            vertexData[vertexOffset + 4] = new VertexPositionNormalTexture(new Vector3(x + 1, 0, z) + blockPosition, Vector3.UnitX, new Vector2((i + 1) / 8.0f, 1.0f));
            vertexData[vertexOffset + 5] = new VertexPositionNormalTexture(new Vector3(x + 1, 2, z) + blockPosition, Vector3.UnitX, new Vector2((i + 1) / 8.0f, 0.5f));
            vertexData[vertexOffset + 6] = new VertexPositionNormalTexture(new Vector3(x + 1, 2, z - 1) + blockPosition, Vector3.UnitX, new Vector2(i / 8.0f, 0.5f));
            vertexData[vertexOffset + 7] = new VertexPositionNormalTexture(new Vector3(x + 1, 0, z - 1) + blockPosition, Vector3.UnitX, new Vector2(i / 8.0f, 1.0f));

            indexData[indexOffset + 6] = (short)(vertexOffset + 4);
            indexData[indexOffset + 7] = (short)(vertexOffset + 5);
            indexData[indexOffset + 8] = (short)(vertexOffset + 6);
            indexData[indexOffset + 9] = (short)(vertexOffset + 6);
            indexData[indexOffset + 10] = (short)(vertexOffset + 7);
            indexData[indexOffset + 11] = (short)(vertexOffset + 4);

            //left face
            vertexData[vertexOffset + 8] = new VertexPositionNormalTexture(new Vector3(x, 0, z) + blockPosition, -Vector3.UnitX, new Vector2((i + 1) / 8.0f, 1.0f));
            vertexData[vertexOffset + 9] = new VertexPositionNormalTexture(new Vector3(x, 2, z) + blockPosition, -Vector3.UnitX, new Vector2((i + 1) / 8.0f, 0.5f));
            vertexData[vertexOffset + 10] = new VertexPositionNormalTexture(new Vector3(x, 2, z - 1) + blockPosition, -Vector3.UnitX, new Vector2(i / 8.0f, 0.5f));
            vertexData[vertexOffset + 11] = new VertexPositionNormalTexture(new Vector3(x, 0, z - 1) + blockPosition, -Vector3.UnitX, new Vector2(i / 8.0f, 1.0f));

            indexData[indexOffset + 12] = (short)(vertexOffset + 8);
            indexData[indexOffset + 13] = (short)(vertexOffset + 11);
            indexData[indexOffset + 14] = (short)(vertexOffset + 10);
            indexData[indexOffset + 15] = (short)(vertexOffset + 10);
            indexData[indexOffset + 16] = (short)(vertexOffset + 9);
            indexData[indexOffset + 17] = (short)(vertexOffset + 8);

            //up face
            vertexData[vertexOffset + 12] = new VertexPositionNormalTexture(new Vector3(x, 2, z) + blockPosition, Vector3.UnitY, new Vector2(j / 8.0f, 0.5f + ((i + 1) / 8.0f)));
            vertexData[vertexOffset + 13] = new VertexPositionNormalTexture(new Vector3(x, 2, z - 1) + blockPosition, Vector3.UnitY, new Vector2(j / 8.0f, 0.5f + (i / 8.0f)));
            vertexData[vertexOffset + 14] = new VertexPositionNormalTexture(new Vector3(x + 1, 2, z - 1) + blockPosition, Vector3.UnitY, new Vector2((j + 1) / 8.0f, 0.5f + (i / 8.0f)));
            vertexData[vertexOffset + 15] = new VertexPositionNormalTexture(new Vector3(x + 1, 2, z) + blockPosition, Vector3.UnitY, new Vector2((j + 1) / 8.0f, 0.5f + ((i + 1) / 8.0f)));

            indexData[indexOffset + 18] = (short)(vertexOffset + 12);
            indexData[indexOffset + 19] = (short)(vertexOffset + 13);
            indexData[indexOffset + 20] = (short)(vertexOffset + 14);
            indexData[indexOffset + 21] = (short)(vertexOffset + 14);
            indexData[indexOffset + 22] = (short)(vertexOffset + 15);
            indexData[indexOffset + 23] = (short)(vertexOffset + 12);
        }

        void fillSteelBrickData(int i, int j, Vector3 blockPosition, int vertexOffset, int indexOffset, ref VertexPositionNormalTexture[] vertexData, ref short[] indexData)
        {
            //the position of the bottom-left vertex
            float x = (j - 1) * 2;
            float z = i * 2;

            //front face
            vertexData[vertexOffset + 0] = new VertexPositionNormalTexture(new Vector3(x, 0, z) + blockPosition, Vector3.UnitZ, new Vector2(0.5f + j / 4.0f, 1.0f));
            vertexData[vertexOffset + 1] = new VertexPositionNormalTexture(new Vector3(x, 2, z) + blockPosition, Vector3.UnitZ, new Vector2(0.5f + j / 4.0f, 0.5f));
            vertexData[vertexOffset + 2] = new VertexPositionNormalTexture(new Vector3(x + 2, 2, z) + blockPosition, Vector3.UnitZ, new Vector2(0.5f + (j + 1) / 4.0f, 0.5f));
            vertexData[vertexOffset + 3] = new VertexPositionNormalTexture(new Vector3(x + 2, 0, z) + blockPosition, Vector3.UnitZ, new Vector2(0.5f + (j + 1) / 4.0f, 1.0f));

            indexData[indexOffset + 0] = (short)(vertexOffset + 0);
            indexData[indexOffset + 1] = (short)(vertexOffset + 1);
            indexData[indexOffset + 2] = (short)(vertexOffset + 2);
            indexData[indexOffset + 3] = (short)(vertexOffset + 2);
            indexData[indexOffset + 4] = (short)(vertexOffset + 3);
            indexData[indexOffset + 5] = (short)(vertexOffset + 0);

            //right face
            vertexData[vertexOffset + 4] = new VertexPositionNormalTexture(new Vector3(x + 2, 0, z) + blockPosition, Vector3.UnitX, new Vector2(0.5f + (i + 1) / 4.0f, 1.0f));
            vertexData[vertexOffset + 5] = new VertexPositionNormalTexture(new Vector3(x + 2, 2, z) + blockPosition, Vector3.UnitX, new Vector2(0.5f + (i + 1) / 4.0f, 0.5f));
            vertexData[vertexOffset + 6] = new VertexPositionNormalTexture(new Vector3(x + 2, 2, z - 2) + blockPosition, Vector3.UnitX, new Vector2(0.5f + i / 4.0f, 0.5f));
            vertexData[vertexOffset + 7] = new VertexPositionNormalTexture(new Vector3(x + 2, 0, z - 2) + blockPosition, Vector3.UnitX, new Vector2(0.5f + i / 4.0f, 1.0f));

            indexData[indexOffset + 6] = (short)(vertexOffset + 4);
            indexData[indexOffset + 7] = (short)(vertexOffset + 5);
            indexData[indexOffset + 8] = (short)(vertexOffset + 6);
            indexData[indexOffset + 9] = (short)(vertexOffset + 6);
            indexData[indexOffset + 10] = (short)(vertexOffset + 7);
            indexData[indexOffset + 11] = (short)(vertexOffset + 4);

            //left face
            vertexData[vertexOffset + 8] = new VertexPositionNormalTexture(new Vector3(x, 0, z) + blockPosition, -Vector3.UnitX, new Vector2(0.5f + (i + 1) / 4.0f, 1.0f));
            vertexData[vertexOffset + 9] = new VertexPositionNormalTexture(new Vector3(x, 2, z) + blockPosition, -Vector3.UnitX, new Vector2(0.5f + (i + 1) / 4.0f, 0.5f));
            vertexData[vertexOffset + 10] = new VertexPositionNormalTexture(new Vector3(x, 2, z - 2) + blockPosition, -Vector3.UnitX, new Vector2(0.5f + i / 4.0f, 0.5f));
            vertexData[vertexOffset + 11] = new VertexPositionNormalTexture(new Vector3(x, 0, z - 2) + blockPosition, -Vector3.UnitX, new Vector2(0.5f + i / 4.0f, 1.0f));

            indexData[indexOffset + 12] = (short)(vertexOffset + 8);
            indexData[indexOffset + 13] = (short)(vertexOffset + 11);
            indexData[indexOffset + 14] = (short)(vertexOffset + 10);
            indexData[indexOffset + 15] = (short)(vertexOffset + 10);
            indexData[indexOffset + 16] = (short)(vertexOffset + 9);
            indexData[indexOffset + 17] = (short)(vertexOffset + 8);

            //up face
            vertexData[vertexOffset + 12] = new VertexPositionNormalTexture(new Vector3(x, 2, z) + blockPosition, Vector3.UnitY, new Vector2(0.51f + j / 4.0f, 0.51f + (i + 1) / 4.0f));
            vertexData[vertexOffset + 13] = new VertexPositionNormalTexture(new Vector3(x, 2, z - 2) + blockPosition, Vector3.UnitY, new Vector2(0.51f + j / 4.0f, 0.51f + i / 4.0f));
            vertexData[vertexOffset + 14] = new VertexPositionNormalTexture(new Vector3(x + 2, 2, z - 2) + blockPosition, Vector3.UnitY, new Vector2(0.51f + (j + 1) / 4.0f, 0.51f + i / 4.0f));
            vertexData[vertexOffset + 15] = new VertexPositionNormalTexture(new Vector3(x + 2, 2, z) + blockPosition, Vector3.UnitY, new Vector2(0.51f + (j + 1) / 4.0f, 0.51f + (i + 1) / 4.0f));

            indexData[indexOffset + 18] = (short)(vertexOffset + 12);
            indexData[indexOffset + 19] = (short)(vertexOffset + 13);
            indexData[indexOffset + 20] = (short)(vertexOffset + 14);
            indexData[indexOffset + 21] = (short)(vertexOffset + 14);
            indexData[indexOffset + 22] = (short)(vertexOffset + 15);
            indexData[indexOffset + 23] = (short)(vertexOffset + 12);
        }

        //aggiorna l'index buffer togliendo gli indici dei mattoni che sono stati distrutti
        //(o, meglio, li fa puntare ad un vertice "spazzatura")
        public void Update()
        {
            foreach (Tuple<int, int> blockIndexes in GameState.gameState.ChangedBlockList.Value)
           {
               int bufferSelector = 0;
               if(blockIndexes.Item1 > 6) bufferSelector = 1;

               int indexOffset = blockIndexesInfos[blockIndexes.Item1, blockIndexes.Item2].startIndexOffset;

               bool[,] bricksGrid = Types.getBricks(GameState.gameState.BlockGrid[blockIndexes.Item1, blockIndexes.Item2].makeUp).Value;

               foreach (Tuple<short, short> index in blockIndexesInfos[blockIndexes.Item1, blockIndexes.Item2].bricksIndex)
               {
                   //se il mattoncino è false fai puntare gli indici al vertice "spazzatura"
                   if((!bricksGrid[index.Item1, index.Item2]) && (indexData[bufferSelector][indexOffset] != (short)(brickCount[bufferSelector] * 16)))
                   {
                       for (int i = 0; i < 24; ++i)
                       {
                           indexData[bufferSelector][indexOffset + i] = (short)(brickCount[bufferSelector] * 16);
                       }
                   }

                   indexOffset += 24;
               }
           }

           indexBuffers[0].SetData<short>(indexData[0]);
           indexBuffers[1].SetData<short>(indexData[1]);
        }

        public void Draw()
        {
            graphicsDevice.Indices = indexBuffers[0];
            graphicsDevice.SetVertexBuffer(vertexBuffers[0]);
            graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertexBuffers[0].VertexCount, 0, PrimitiveCount(0));

            graphicsDevice.Indices = indexBuffers[1];
            graphicsDevice.SetVertexBuffer(vertexBuffers[1]);
            graphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertexBuffers[1].VertexCount, 0, PrimitiveCount(1));
        }
    }
}
